#pragma once

#include "num.h"
#include "str.h"

namespace sfc::fmt {

using slice::Slice;
using str::Str;
using tuple::Tuple;

template <class W>
struct Formatter;

struct Style {
  /**
   * [[fill]align][sign]['#'][width]['.' precision][type]
   */
  i8 _fill = 0;    //: *
  i8 _align = 0;   //: <>^=
  i8 _sign = 0;    //: +-
  i8 _prefix = 0;  //: #
  u8 _width = 0;   //: [0-9]*
  i8 _point = 0;   //: [.]
  u8 _prec = 0;    //: [0-9]*
  i8 _type = 0;    //: *

  u8 _desc_len = 0;
  char _desc_arr[7] = {};

  static auto xnew(Str s) -> Style;

  [[sfc_inline]] auto fill(char def_val) const -> char {
    return _fill != 0 ? _fill : def_val;
  }

  [[sfc_inline]] auto align(char def_val = 0) const -> char {
    return _align != 0 ? _align : def_val;
  }

  [[sfc_inline]] auto type(char def_val = 0) const -> char {
    return _type != 0 ? _type : def_val;
  }

  [[sfc_inline]] auto width(usize def_val = 0) const -> usize {
    return _width != 0 ? _width : def_val;
  }

  [[sfc_inline]] auto precision(usize def_val = 0) const -> usize {
    return _point == '.' ? _prec : def_val;
  }

  [[sfc_inline]] auto desc() const -> Str {
    return Str{_desc_arr, usize(_desc_len)};
  }

  auto _pad_num(auto val, Slice<char> buf) const -> Str;
};

template <class W>
struct Write : W {
  ~Write();

  using W::write_str;

  void write_chars(char val, usize cnt) {
    const auto S = "                                                                ";
    char buf[64];
    auto len = cmp::min(cnt, sizeof(buf));
    auto ptr = val == ' ' ? S : (ptr::fill(buf, val, len), buf);
    this->write_str({ptr, len});
  }
};

template <class T>
struct Display {
  T _0;

  template <class W>
  void format(Formatter<W>& f) const;
};

template <class... T>
struct Args;

template <class W>
struct Formatter : Style {
  Write<W>& _out;
  usize _depth = 0;

  [[sfc_inline]] explicit Formatter(W& w) noexcept : _out{static_cast<Write<W>&>(w)} {}

  [[sfc_inline]] void write_str(Str s) {
    _out.write_str(s);
  }

  template <class... T>
  void write(const T&... args) {
    if constexpr (sizeof...(T) == 1) {
      if constexpr ((__is_class(T) && ...)) {
        (args.format(*this), ...);
      } else {
        (reinterpret_cast<const Display<T>&>(args).format(*this), ...);
      }
    } else {
      Args<T...>{args...}.format(*this);
    }
  }

  void pad(Str s) {
    if (this->_width <= s.len()) {
      return this->write_str(s);
    }

    const auto c = this->fill(' ');
    const auto n = this->width() - s.len();

    switch (_align) {
      default:
      case '>':
        _out.write_chars(c, n);
        _out.write_str(s);
        break;
      case '<':
        _out.write_str(s);
        _out.write_chars(c, n);
        break;
      case '^':
      case '=':
        _out.write_chars(c, (n + 0) / 2);
        _out.write_str(s);
        _out.write_chars(c, (n + 1) / 2);
        break;
    }
  }

  struct Box;

  template <usize N>
  auto box(const char (&pattern)[N], bool verbose = false) -> Box;
};

template <class W>
Formatter(W&) -> Formatter<W>;

void write(auto& w, const auto&... args) {
  auto f = Formatter{w};
  f.write(args...);
}

void writeln(auto& w, const auto&... args) {
  auto f = Formatter{w};
  f.write(args...);
  w.write_str("\n");
}

struct Pattern {
  Str _0;
  auto next() -> Tuple<Str, Str>;

  template <class W>
  void format(Formatter<W>& f, const auto& val) {
    auto [a, b] = this->next();
    f.write_str(a);
    if (b.len() < 2) {
      return;
    }
    mem::replace<Style>(f, Style::xnew({b._ptr + 1, b._len - 2}));
    f.write(val);
  }
};

template <class S, class... T>
struct Args<S, T...> {
  using Any = const void*;

  template <usize I>
  using U = __type_pack_element<I, T...>;

  static constexpr auto N = sizeof...(T);

  Pattern _pat;
  Any _args[N];

  [[sfc_inline]] Args(const S& s, const T&... args) : _pat{s}, _args{&args...} {}

  template <class W>
  void format(Formatter<W>& f) const {
    auto pat = _pat;
    auto tmp = Style(f);
    if constexpr (N > 0) pat.format(f, *static_cast<const U<0>*>(_args[0]));
    if constexpr (N > 1) pat.format(f, *static_cast<const U<1>*>(_args[1]));
    if constexpr (N > 2) pat.format(f, *static_cast<const U<2>*>(_args[2]));
    if constexpr (N > 3) pat.format(f, *static_cast<const U<3>*>(_args[3]));
    if constexpr (N > 4) pat.format(f, *static_cast<const U<4>*>(_args[4]));
    if constexpr (N > 5) pat.format(f, *static_cast<const U<5>*>(_args[5]));
    if constexpr (N > 6) pat.format(f, *static_cast<const U<6>*>(_args[6]));
    if constexpr (N > 7) pat.format(f, *static_cast<const U<7>*>(_args[7]));
    if constexpr (N > 8) pat.format(f, *static_cast<const U<8>*>(_args[8]));
    if constexpr (N > 9) pat.format(f, *static_cast<const U<9>*>(_args[9]));
    f.write_str(pat._0);
    mem::replace<Style>(f, tmp);
  }
};

template <class W>
struct Formatter<W>::Box {
  Formatter& _fmt;
  Str _pat[3];
  usize _cnt = 0;
  bool _verbose;

  Box(Formatter& f, const Str (&v)[3], bool verbose)
      : _fmt{f}, _pat{v[0], v[1], v[2]}, _verbose{verbose} {
    _fmt._out.write_str(_pat[0]);
    _fmt._depth += 1;
  }

  ~Box() {
    _fmt._depth -= 1;
    if (_cnt != 0 && _verbose) {
      _fmt._out.write_str("\n");
      _fmt._out.write_chars(' ', _fmt._depth * 4);
    }
    _fmt._out.write_str(_pat[2]);
  }

  Box(const Box&&) = delete;

  auto entry(const auto&... args) -> Box& {
    if (_cnt != 0) {
      _fmt._out.write_str(_pat[1]);
    }
    if (_verbose) {
      _fmt._out.write_str("\n");
      _fmt._out.write_chars(' ', _fmt._depth * 4);
    } else if (_cnt != 0) {
      _fmt._out.write_str(" ");
    }
    _cnt += 1;
    _fmt.write(args...);
    return *this;
  }
};

template <class W>
template <usize N>
auto Formatter<W>::box(const char (&p)[N], bool verbose) -> Box {
  static_assert(N <= 4);
  const auto v = verbose || this->desc() == Str("|");
  if constexpr (N == 1) {
    return {*this, {"", "", ""}, v};
  } else if constexpr (N == 2) {
    return {*this, {"", {p, 1}, ""}, v};
  } else if constexpr (N == 3) {
    return {*this, {{p + 0, 1}, ",", {p + 1, 1}}, v};
  } else if constexpr (N == 4) {
    return {*this, {{p + 0, 1}, {p + 1, 1}, {p + 2, 1}}, v};
  }
}

template <>
template <class W>
void Display<bool>::format(Formatter<W>& f) const {
  const auto s = _0 ? Str("true") : Str("false");
  f.write_str(s);
}

template <>
template <class W>
void Display<signed char>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(i32(_0), buf));
}

template <>
template <class W>
void Display<signed short>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(i32(_0), buf));
}

template <>
template <class W>
void Display<signed int>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(i32(_0), buf));
}

template <>
template <class W>
void Display<signed long>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(i64(_0), buf));
}

template <>
template <class W>
void Display<signed long long>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(i64(_0), buf));
}

template <>
template <class W>
void Display<unsigned char>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(u32(_0), buf));
}

template <>
template <class W>
void Display<unsigned short>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(u32(_0), buf));
}

template <>
template <class W>
void Display<unsigned int>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(u32(_0), buf));
}

template <>
template <class W>
void Display<unsigned long>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(u64(_0), buf));
}

template <>
template <class W>
void Display<unsigned long long>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(u64(_0), buf));
}

template <>
template <class W>
void Display<float>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(f64(_0), buf));
}

template <>
template <class W>
void Display<double>::format(Formatter<W>& f) const {
  char buf[64];
  f.write_str(f._pad_num(f64(_0), buf));
}

template <class T>
struct Display<T*> {
  const void* _0;

  template <class W>
  void format(Formatter<W>& f) const {
    char buf[64];
    f.write_str(f._pad_num(_0, buf));
  }
};

template <class T, usize N>
struct Display<T[N]> {
  T _0[N];

  template <class W>
  void format(Formatter<W>& f) const {
    if constexpr (__is_same(T, char)) {
      f.pad(_val);
    } else {
      Slice{_val, N}.format(f);
    }
  }
};

}  // namespace sfc::fmt
